Перейти к основному содержимому

Константы и статические переменные

В этой главе мы изучим константы (const) и статические переменные (static) — специальные типы данных в Rust, которые существуют в течение всего времени выполнения программы. Понимание их различий и правильное применение критически важно для написания эффективного кода.

Константы (const)

Константы в Rust — это значения, которые вычисляются во время компиляции и встраиваются непосредственно в код везде, где используются.

Объявление констант

Базовое объявление констант
// Константы объявляются на уровне модуля
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265358979323846;
const APP_NAME: &str = "My Rust Application";
const DEBUG_MODE: bool = true;

fn main() {
println!("Максимальные очки: {}", MAX_POINTS);
println!("Число π: {}", PI);
println!("Имя приложения: {}", APP_NAME);
println!("Режим отладки: {}", DEBUG_MODE);

// Константы можно объявлять внутри функций
const LOCAL_CONSTANT: i32 = 42;
println!("Локальная константа: {}", LOCAL_CONSTANT);
}

Правила для констант

📝 Обязательная типизация

Тип константы должен быть указан явно

const VALUE: i32 = 42;

🔒 Всегда неизменяемы

Нельзя использовать mut с константами

// const mut X: i32 = 42; // ❌ Ошибка

⏰ Вычисление на этапе компиляции

Значение должно быть известно компилятору

const RESULT: i32 = 10 + 20;

🏷️ Именование SCREAMING_SNAKE_CASE

Принятое соглашение для констант

const MAX_BUFFER_SIZE: usize = 8192;

Константные выражения

Rust позволяет использовать в константах довольно сложные выражения:

Арифметика в константах
const SECONDS_IN_MINUTE: u32 = 60;
const MINUTES_IN_HOUR: u32 = 60;
const HOURS_IN_DAY: u32 = 24;

// Составные константы
const SECONDS_IN_HOUR: u32 = SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
const SECONDS_IN_DAY: u32 = SECONDS_IN_HOUR * HOURS_IN_DAY;
const MINUTES_IN_DAY: u32 = MINUTES_IN_HOUR * HOURS_IN_DAY;

// Математические вычисления
const CIRCLE_AREA: f64 = PI * 5.0 * 5.0; // Площадь круга с радиусом 5
const SQUARE_ROOT_OF_2: f64 = 1.4142135623730951;

fn main() {
println!("Секунд в часе: {}", SECONDS_IN_HOUR);
println!("Секунд в дне: {}", SECONDS_IN_DAY);
println!("Минут в дне: {}", MINUTES_IN_DAY);
println!("Площадь круга: {}", CIRCLE_AREA);
println!("√2 ≈ {}", SQUARE_ROOT_OF_2);
}

Константы в различных контекстах

Использование констант в разных местах
// Глобальные константы модуля
const BUFFER_SIZE: usize = 8192;
const MAX_CONNECTIONS: u32 = 100;

struct Config {
buffer_size: usize,
max_connections: u32,
timeout_seconds: u64,
}

impl Config {
// Константы внутри impl блока
const DEFAULT_TIMEOUT: u64 = 30;
const MAX_TIMEOUT: u64 = 300;

fn new() -> Self {
Self {
buffer_size: BUFFER_SIZE, // Использование глобальной константы
max_connections: MAX_CONNECTIONS,
timeout_seconds: Self::DEFAULT_TIMEOUT, // Использование константы impl
}
}

fn with_timeout(mut self, timeout: u64) -> Self {
// Константа внутри функции
const MIN_TIMEOUT: u64 = 1;

self.timeout_seconds = timeout.clamp(MIN_TIMEOUT, Self::MAX_TIMEOUT);
self
}
}

fn main() {
let config = Config::new()
.with_timeout(60);

println!("Размер буфера: {}", config.buffer_size);
println!("Максимум подключений: {}", config.max_connections);
println!("Таймаут: {} секунд", config.timeout_seconds);

// Константы в match выражениях
const SUCCESS_CODE: i32 = 0;
const ERROR_CODE: i32 = 1;

let result = SUCCESS_CODE;
match result {
SUCCESS_CODE => println!("Успех!"),
ERROR_CODE => println!("Ошибка!"),
_ => println!("Неизвестный код"),
}
}

Статические переменные (static)

Статические переменные имеют фиксированный адрес в памяти и существуют в течение всего времени выполнения программы.

Объявление статических переменных

Базовые статические переменные
static GLOBAL_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);

static APP_VERSION: &str = "2.1.0";
static MAX_RETRIES: u8 = 3;
static PI_STATIC: f64 = 3.14159265358979323846;

fn main() {
println!("Версия приложения: {}", APP_VERSION);
println!("Максимум повторов: {}", MAX_RETRIES);
println!("π (статическая): {}", PI_STATIC);

// Работа с атомарным счётчиком
let old_value = GLOBAL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
println!("Счётчик: {} -> {}", old_value, old_value + 1);

// Получение адреса статической переменной
let version_ptr = &APP_VERSION as *const &str;
println!("Адрес APP_VERSION: {:p}", version_ptr);
}

Мутабельные статические переменные

Небезопасность

Мутабельные статические переменные небезопасны и требуют использования unsafe блоков!

Мутабельные статические переменные
static mut GLOBAL_STATE: i32 = 0;
static mut MESSAGE: &str = "Начальное сообщение";

// Более безопасная альтернатива с Mutex
use std::sync::Mutex;
static SAFE_COUNTER: Mutex<i32> = Mutex::new(0);

fn main() {
// Небезопасная работа с мутабельной статической переменной
unsafe {
GLOBAL_STATE += 1;
println!("Глобальное состояние: {}", GLOBAL_STATE);

MESSAGE = "Изменённое сообщение";
println!("Сообщение: {}", MESSAGE);
}

// Безопасная работа с Mutex
{
let mut counter = SAFE_COUNTER.lock().unwrap();
*counter += 1;
println!("Безопасный счётчик: {}", *counter);
} // Mutex автоматически освобождается

// Многопоточный доступ к безопасному счётчику
use std::thread;

let handles: Vec<_> = (0..5).map(|i| {
thread::spawn(move || {
let mut counter = SAFE_COUNTER.lock().unwrap();
*counter += 1;
println!("Поток {} увеличил счётчик до {}", i, *counter);
})
}).collect();

for handle in handles {
handle.join().unwrap();
}
}

Ленивая инициализация статических переменных

Для сложных статических переменных можно использовать ленивую инициализацию:

Ленивая инициализация с OnceLock
use std::sync::OnceLock;
use std::collections::HashMap;

// Статическая переменная с ленивой инициализацией
static SETTINGS: OnceLock<HashMap<String, String>> = OnceLock::new();

fn get_settings() -> &'static HashMap<String, String> {
SETTINGS.get_or_init(|| {
let mut map = HashMap::new();
map.insert("database_url".to_string(), "postgres://localhost".to_string());
map.insert("port".to_string(), "8080".to_string());
map.insert("debug".to_string(), "true".to_string());
map
})
}

fn main() {
// Первый доступ инициализирует HashMap
let settings = get_settings();
println!("База данных: {}", settings.get("database_url").unwrap());
println!("Порт: {}", settings.get("port").unwrap());

// Последующие обращения используют уже инициализированное значение
let settings_again = get_settings();
println!("Отладка: {}", settings_again.get("debug").unwrap());

// Проверяем, что это один и тот же объект
println!("Тот же объект: {}",
std::ptr::eq(settings, settings_again));
}

Сравнение const и static

Понимание различий между const и static критически важно:

Аспектconststatic
Время вычисленияВо время компиляцииПри первом обращении
Расположение в памятиВстраивается в код (inlined)Фиксированный адрес в памяти
МутабельностьВсегда неизменяемыМогут быть мутабельными (unsafe)
Размер бинарного файлаМожет увеличить размерОдна копия в памяти
Использование памятиКопия в каждом месте использованияОдно место в памяти
Получение адресаНевозможноВозможно
МногопоточностьНет проблемТребует синхронизации для mut

Практическое сравнение

Демонстрация различий const и static
const CONST_VALUE: i32 = 42;
static STATIC_VALUE: i32 = 42;

fn main() {
// Нельзя получить адрес константы
// let const_ptr = &CONST_VALUE as *const i32; // ❌ Ошибка в некоторых контекстах

// Можно получить адрес статической переменной
let static_ptr = &STATIC_VALUE as *const i32;
println!("Адрес статической переменной: {:p}", static_ptr);

// Демонстрация inlining константы
let array_const = [CONST_VALUE; 3]; // Значение встроено
let array_static = [STATIC_VALUE; 3]; // Значение загружено из памяти

println!("Массив с константой: {:?}", array_const);
println!("Массив со статической переменной: {:?}", array_static);

// Множественные обращения
for i in 0..5 {
// CONST_VALUE встраивается в каждое место использования
println!("Константа #{}: {}", i, CONST_VALUE);

// STATIC_VALUE загружается из одного места в памяти
println!("Статическая #{}: {}", i, STATIC_VALUE);
}
}

// Функция для демонстрации inlining
fn use_const() -> i32 {
CONST_VALUE * 2 // Компилятор может оптимизировать это в 84
}

fn use_static() -> i32 {
STATIC_VALUE * 2 // Требует загрузки значения из памяти
}

Когда использовать const vs static

Используйте const когда:

✅ Хорошие случаи для const
// Математические константы
const PI: f64 = 3.14159265358979323846;
const E: f64 = 2.71828182845904523536;

// Конфигурационные значения
const MAX_BUFFER_SIZE: usize = 8192;
const DEFAULT_TIMEOUT: u64 = 30;

// Магические числа
const HTTP_OK: u16 = 200;
const HTTP_NOT_FOUND: u16 = 404;

// Битовые маски
const READ_PERMISSION: u8 = 0b100;
const WRITE_PERMISSION: u8 = 0b010;
const EXECUTE_PERMISSION: u8 = 0b001;

fn main() {
let area = PI * 5.0 * 5.0;
println!("Площадь круга: {}", area);

let permissions = READ_PERMISSION | WRITE_PERMISSION;
println!("Права доступа: {:03b}", permissions);
}

Используйте static когда:

✅ Хорошие случаи для static
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;

// Глобальные счётчики
static REQUEST_COUNTER: AtomicUsize = AtomicUsize::new(0);

// Глобальные настройки (неизменяемые)
static APPLICATION_NAME: &str = "My Web Server";
static BUILD_VERSION: &str = env!("CARGO_PKG_VERSION");

// Разделяемое состояние (с синхронизацией)
static SHARED_DATA: Mutex<Vec<String>> = Mutex::new(Vec::new());

// Большие неизменяемые данные
static LOOKUP_TABLE: [u64; 1000] = [0; 1000]; // Инициализация нулями

fn increment_requests() {
REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
}

fn get_request_count() -> usize {
REQUEST_COUNTER.load(Ordering::Relaxed)
}

fn main() {
println!("Приложение: {}", APPLICATION_NAME);
println!("Версия: {}", BUILD_VERSION);

// Симуляция обработки запросов
for _ in 0..5 {
increment_requests();
}

println!("Обработано запросов: {}", get_request_count());

// Работа с разделяемыми данными
{
let mut data = SHARED_DATA.lock().unwrap();
data.push("Новая запись".to_string());
println!("Записей в shared data: {}", data.len());
}
}

Константы времени компиляции

Rust поддерживает вычисления во время компиляции с помощью const fn:

Константные функции

Константные функции (const fn)
const fn factorial(n: u64) -> u64 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}

const fn fibonacci(n: u32) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}

const fn is_power_of_two(n: u32) -> bool {
n != 0 && (n & (n - 1)) == 0
}

// Использование const fn в константах
const FACTORIAL_10: u64 = factorial(10);
const FIB_20: u64 = fibonacci(20);
const IS_POWER_OF_TWO_16: bool = is_power_of_two(16);

fn main() {
println!("10! = {}", FACTORIAL_10); // 3628800
println!("fib(20) = {}", FIB_20); // 6765
println!("16 - степень двойки: {}", IS_POWER_OF_TWO_16); // true

// Можно использовать const fn и в runtime
let runtime_factorial = factorial(5);
println!("5! = {}", runtime_factorial); // 120

// Константы в размерах массивов
const ARRAY_SIZE: usize = factorial(4) as usize;
let buffer: [u8; ARRAY_SIZE] = [0; ARRAY_SIZE];
println!("Размер массива: {}", buffer.len()); // 24
}

Ограничения const fn

Что можно и нельзя в const fn
// ✅ Разрешено в const fn
const fn allowed_operations(x: i32, y: i32) -> i32 {
// Арифметические операции
let sum = x + y;
let product = x * y;

// Условные выражения
let result = if x > y { sum } else { product };

// Циклы
let mut counter = 0;
let mut i = 0;
while i < 10 {
counter += 1;
i += 1;
}

// Match выражения
match result {
0 => counter,
_ => result + counter,
}
}

// ❌ Не разрешено в const fn (на момент написания)
/*
const fn not_allowed() {
// Динамическое выделение памяти
let v = Vec::new(); // ❌

// Вызов не-const функций
println!("Hello"); // ❌

// Работа с указателями (в большинстве случаев)
let ptr = &42 as *const i32; // ❌

// Использование внешних библиотек
std::thread::sleep(std::time::Duration::from_secs(1)); // ❌
}
*/

const COMPUTED_VALUE: i32 = allowed_operations(10, 20);

fn main() {
println!("Вычисленное значение: {}", COMPUTED_VALUE);
}

Практические примеры

Система конфигурации

Комплексная система конфигурации
use std::sync::OnceLock;
use std::collections::HashMap;

// Константы для значений по умолчанию
const DEFAULT_PORT: u16 = 8080;
const DEFAULT_HOST: &str = "127.0.0.1";
const DEFAULT_MAX_CONNECTIONS: u32 = 1000;
const DEFAULT_TIMEOUT_MS: u64 = 5000;

// Константы для ограничений
const MIN_PORT: u16 = 1024;
const MAX_PORT: u16 = 65535;
const MIN_CONNECTIONS: u32 = 1;
const MAX_CONNECTIONS: u32 = 10000;

// Статическая конфигурация с ленивой инициализацией
static CONFIG: OnceLock<AppConfig> = OnceLock::new();

#[derive(Debug)]
struct AppConfig {
host: String,
port: u16,
max_connections: u32,
timeout_ms: u64,
features: HashMap<String, bool>,
}

impl AppConfig {
const fn validate_port(port: u16) -> bool {
port >= MIN_PORT && port <= MAX_PORT
}

const fn validate_connections(connections: u32) -> bool {
connections >= MIN_CONNECTIONS && connections <= MAX_CONNECTIONS
}

fn new() -> Self {
let mut features = HashMap::new();
features.insert("logging".to_string(), true);
features.insert("metrics".to_string(), false);
features.insert("debug".to_string(), cfg!(debug_assertions));

Self {
host: DEFAULT_HOST.to_string(),
port: DEFAULT_PORT,
max_connections: DEFAULT_MAX_CONNECTIONS,
timeout_ms: DEFAULT_TIMEOUT_MS,
features,
}
}

fn load_from_env() -> Self {
let mut config = Self::new();

// Загрузка из переменных окружения
if let Ok(port_str) = std::env::var("APP_PORT") {
if let Ok(port) = port_str.parse::<u16>() {
if Self::validate_port(port) {
config.port = port;
}
}
}

if let Ok(host) = std::env::var("APP_HOST") {
config.host = host;
}

if let Ok(max_conn_str) = std::env::var("APP_MAX_CONNECTIONS") {
if let Ok(max_conn) = max_conn_str.parse::<u32>() {
if Self::validate_connections(max_conn) {
config.max_connections = max_conn;
}
}
}

config
}
}

fn get_config() -> &'static AppConfig {
CONFIG.get_or_init(AppConfig::load_from_env)
}

fn main() {
// Первое обращение инициализирует конфигурацию
let config = get_config();
println!("Конфигурация приложения:");
println!(" Хост: {}", config.host);
println!(" Порт: {}", config.port);
println!(" Макс. соединений: {}", config.max_connections);
println!(" Таймаут: {} мс", config.timeout_ms);

println!(" Функции:");
for (feature, enabled) in &config.features {
println!(" {}: {}", feature, if *enabled { "включена" } else { "отключена" });
}

// Проверка валидации констант
println!("\nВалидация:");
println!(" Порт 80 валиден: {}", AppConfig::validate_port(80));
println!(" Порт 8080 валиден: {}", AppConfig::validate_port(8080));
println!(" 1000000 соединений валидно: {}", AppConfig::validate_connections(1000000));
}

Система метрик

Глобальная система метрик
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use std::sync::OnceLock;

// Константы для метрик
const METRICS_UPDATE_INTERVAL_MS: u64 = 1000;
const MAX_RESPONSE_TIME_MS: u64 = 30000;

// Атомарные счётчики для метрик
static REQUESTS_TOTAL: AtomicU64 = AtomicU64::new(0);
static REQUESTS_SUCCESS: AtomicU64 = AtomicU64::new(0);
static REQUESTS_ERROR: AtomicU64 = AtomicU64::new(0);
static RESPONSE_TIME_TOTAL_MS: AtomicU64 = AtomicU64::new(0);
static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0);

// Статическая переменная для времени старта
static START_TIME: OnceLock<Instant> = OnceLock::new();

struct Metrics;

impl Metrics {
fn init() {
START_TIME.get_or_init(Instant::now);
}

fn record_request_start() {
REQUESTS_TOTAL.fetch_add(1, Ordering::Relaxed);
ACTIVE_CONNECTIONS.fetch_add(1, Ordering::Relaxed);
}

fn record_request_end(success: bool, duration: Duration) {
ACTIVE_CONNECTIONS.fetch_sub(1, Ordering::Relaxed);

if success {
REQUESTS_SUCCESS.fetch_add(1, Ordering::Relaxed);
} else {
REQUESTS_ERROR.fetch_add(1, Ordering::Relaxed);
}

let duration_ms = duration.as_millis() as u64;
RESPONSE_TIME_TOTAL_MS.fetch_add(duration_ms, Ordering::Relaxed);
}

fn get_stats() -> MetricsSnapshot {
let total = REQUESTS_TOTAL.load(Ordering::Relaxed);
let success = REQUESTS_SUCCESS.load(Ordering::Relaxed);
let error = REQUESTS_ERROR.load(Ordering::Relaxed);
let response_time_total = RESPONSE_TIME_TOTAL_MS.load(Ordering::Relaxed);
let active = ACTIVE_CONNECTIONS.load(Ordering::Relaxed);

let uptime = START_TIME.get()
.map(|start| start.elapsed())
.unwrap_or(Duration::ZERO);

let avg_response_time = if total > 0 {
response_time_total / total
} else {
0
};

MetricsSnapshot {
requests_total: total,
requests_success: success,
requests_error: error,
success_rate: if total > 0 { success as f64 / total as f64 } else { 0.0 },
average_response_time_ms: avg_response_time,
active_connections: active,
uptime_seconds: uptime.as_secs(),
}
}

fn reset() {
REQUESTS_TOTAL.store(0, Ordering::Relaxed);
REQUESTS_SUCCESS.store(0, Ordering::Relaxed);
REQUESTS_ERROR.store(0, Ordering::Relaxed);
RESPONSE_TIME_TOTAL_MS.store(0, Ordering::Relaxed);
// ACTIVE_CONNECTIONS не сбрасываем - она отражает текущее состояние
}
}

#[derive(Debug)]
struct MetricsSnapshot {
requests_total: u64,
requests_success: u64,
requests_error: u64,
success_rate: f64,
average_response_time_ms: u64,
active_connections: usize,
uptime_seconds: u64,
}

// Симуляция обработки запроса
fn simulate_request(id: u32, will_succeed: bool) {
println!("Запрос {} начат", id);
Metrics::record_request_start();

let start = Instant::now();

// Симуляция работы
std::thread::sleep(Duration::from_millis(10 + (id % 100) as u64));

let duration = start.elapsed();
Metrics::record_request_end(will_succeed, duration);

println!("Запрос {} завершён ({})",
id,
if will_succeed { "успешно" } else { "с ошибкой" });
}

fn main() {
Metrics::init();

println!("Запуск системы метрик...\n");

// Симуляция нескольких запросов
use std::thread;

let handles: Vec<_> = (0..10).map(|i| {
thread::spawn(move || {
simulate_request(i, i % 4 != 0); // 75% успешных запросов
})
}).collect();

// Ждём завершения всех запросов
for handle in handles {
handle.join().unwrap();
}

// Получаем и выводим статистику
let stats = Metrics::get_stats();
println!("\n=== МЕТРИКИ ===");
println!("Всего запросов: {}", stats.requests_total);
println!("Успешных запросов: {}", stats.requests_success);
println!("Ошибочных запросов: {}", stats.requests_error);
println!("Коэффициент успеха: {:.1}%", stats.success_rate * 100.0);
println!("Среднее время ответа: {} мс", stats.average_response_time_ms);
println!("Активных соединений: {}", stats.active_connections);
println!("Время работы: {} секунд", stats.uptime_seconds);

// Демонстрация сброса метрик
println!("\nСброс метрик...");
Metrics::reset();
let reset_stats = Metrics::get_stats();
println!("После сброса - всего запросов: {}", reset_stats.requests_total);
}

Конфигурация компиляции

Константы для разных режимов сборки
// Константы, зависящие от режима сборки
const LOG_LEVEL: &str = if cfg!(debug_assertions) {
"DEBUG"
} else {
"INFO"
};

const BUFFER_SIZE: usize = if cfg!(debug_assertions) {
1024 // Меньший буфер в debug режиме для тестирования
} else {
8192 // Больший буфер в release режиме для производительности
};

const ENABLE_PROFILING: bool = cfg!(debug_assertions);
const ENABLE_METRICS: bool = true;
const ENABLE_TRACING: bool = cfg!(feature = "tracing");

// Константы для различных платформ
const MAX_PATH_LENGTH: usize = if cfg!(target_os = "windows") {
260
} else if cfg!(target_os = "linux") {
4096
} else {
1024
};

const PATH_SEPARATOR: char = if cfg!(target_os = "windows") {
'\\'
} else {
'/'
};

// Архитектурно-зависимые константы
const WORD_SIZE: usize = std::mem::size_of::<usize>();
const IS_64_BIT: bool = WORD_SIZE == 8;

fn main() {
println!("=== КОНФИГУРАЦИЯ СБОРКИ ===");
println!("Уровень логирования: {}", LOG_LEVEL);
println!("Размер буфера: {} байт", BUFFER_SIZE);
println!("Профилирование включено: {}", ENABLE_PROFILING);
println!("Метрики включены: {}", ENABLE_METRICS);
println!("Трейсинг включён: {}", ENABLE_TRACING);

println!("\n=== ПЛАТФОРМА ===");
println!("Операционная система: {}", std::env::consts::OS);
println!("Архитектура: {}", std::env::consts::ARCH);
println!("Максимальная длина пути: {}", MAX_PATH_LENGTH);
println!("Разделитель путей: '{}'", PATH_SEPARATOR);

println!("\n=== АРХИТЕКТУРА ===");
println!("Размер слова: {} байт", WORD_SIZE);
println!("64-битная система: {}", IS_64_BIT);
println!("Порядок байтов: {}", std::env::consts::ENDIAN);

// Условная компиляция в коде
if ENABLE_PROFILING {
println!("\n[ПРОФИЛИРОВАНИЕ] Начало измерения производительности");
}

// Демонстрация работы с путями
let example_path = format!("home{}user{}documents{}file.txt",
PATH_SEPARATOR, PATH_SEPARATOR, PATH_SEPARATOR);
println!("\nПример пути: {}", example_path);

if example_path.len() > MAX_PATH_LENGTH {
println!("Предупреждение: путь превышает максимальную длину!");
}
}

Лучшие практики

1. Правильное именование

✅ Хорошие практики именования
// Константы - SCREAMING_SNAKE_CASE
const MAX_BUFFER_SIZE: usize = 8192;
const DEFAULT_TIMEOUT_SECONDS: u64 = 30;
const PI: f64 = 3.14159265358979323846;

// Статические переменные - SCREAMING_SNAKE_CASE
static GLOBAL_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
static APPLICATION_CONFIG: &str = "production";

// Группировка связанных констант
mod http_status {
pub const OK: u16 = 200;
pub const NOT_FOUND: u16 = 404;
pub const INTERNAL_SERVER_ERROR: u16 = 500;
}

// Или использование enum для группировки
#[repr(u16)]
enum HttpStatus {
Ok = 200,
NotFound = 404,
InternalServerError = 500,
}

fn main() {
println!("HTTP OK: {}", http_status::OK);
println!("HTTP OK (enum): {}", HttpStatus::Ok as u16);
}

2. Документирование констант и статических переменных

✅ Хорошее документирование
/// Максимальный размер буфера для обработки данных.
///
/// Это значение выбрано исходя из баланса между использованием памяти
/// и производительностью. При увеличении значения улучшается
/// производительность, но растёт потребление памяти.
const MAX_BUFFER_SIZE: usize = 8192;

/// Таймаут по умолчанию для HTTP запросов в секундах.
///
/// # Примеры
///
/// ```rust
/// use std::time::Duration;
/// let timeout = Duration::from_secs(DEFAULT_HTTP_TIMEOUT);
/// ```
const DEFAULT_HTTP_TIMEOUT: u64 = 30;

/// Глобальный счётчик обработанных запросов.
///
/// Этот счётчик является потокобезопасным и может использоваться
/// из любого потока для отслеживания общего количества запросов.
///
/// # Безопасность
///
/// Использование атомарных операций гарантирует корректность
/// в многопоточной среде без дополнительной синхронизации.
static REQUEST_COUNTER: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);

fn main() {
println!("Размер буфера: {}", MAX_BUFFER_SIZE);
println!("Таймаут: {} сек", DEFAULT_HTTP_TIMEOUT);

REQUEST_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
println!("Запросов обработано: {}",
REQUEST_COUNTER.load(std::sync::atomic::Ordering::Relaxed));
}

3. Избегание магических чисел

✅ Замена магических чисел константами
// ❌ Плохо: магические числа
fn bad_example() {
let buffer = vec![0u8; 4096]; // Что означает 4096?
std::thread::sleep(std::time::Duration::from_millis(5000)); // Что за 5000?

if buffer.len() > 8192 { // И что это за 8192?
println!("Буфер слишком большой");
}
}

// ✅ Хорошо: осмысленные константы
const DEFAULT_BUFFER_SIZE: usize = 4096; // 4KB - стандартный размер страницы
const NETWORK_TIMEOUT_MS: u64 = 5000; // 5 секунд таймаута сети
const MAX_SAFE_BUFFER_SIZE: usize = 8192; // 8KB - максимальный безопасный размер

fn good_example() {
let buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
std::thread::sleep(std::time::Duration::from_millis(NETWORK_TIMEOUT_MS));

if buffer.len() > MAX_SAFE_BUFFER_SIZE {
println!("Буфер превышает максимальный безопасный размер");
}
}

fn main() {
bad_example();
good_example();
}

Заключение

В этой главе мы детально изучили константы и статические переменные в Rust:

Константы (const) — вычисляются во время компиляции, встраиваются в код ✅ Статические переменные (static) — существуют весь жизненный цикл программы ✅ Различия между const и static — когда что использовать ✅ Мутабельные статические переменные — небезопасность и альтернативы ✅ Ленивую инициализацию — OnceLock, lazy_static, Once ✅ Константные функции (const fn) — вычисления во время компиляции ✅ Практические примеры — конфигурация, метрики, условная компиляция ✅ Лучшие практики — именование, документирование, избегание магических чисел

Правильное использование констант и статических переменных делает код более читаемым, производительным и безопасным. Они являются важными инструментами для создания надёжного системного программного обеспечения на Rust.

Что дальше?

В следующей главе: "Комментарии в коде" — мы изучим различные способы документирования кода в Rust, включая обычные комментарии и документационные комментарии.


Практические задания

  1. Создайте систему конфигурации игры с константами для различных параметров (здоровье, урон, скорость) и статическими переменными для глобального состояния

  2. Реализуйте систему логирования с использованием статических переменных для уровня логирования и константных функций для форматирования сообщений

  3. Напишите библиотеку математических констант с использованием const fn для вычисления значений во время компиляции

  4. Создайте конфигуратор приложения, который использует ленивую инициализацию для загрузки настроек из файла или переменных окружения

Вопросы для самопроверки

  1. В чём основное различие между const и static переменными?
  2. Почему мутабельные статические переменные требуют unsafe?
  3. Что такое const fn и какие ограничения у них есть?
  4. Когда следует использовать ленивую инициализацию статических переменных?
  5. Как правильно организовать константы в больших проектах?

Полезные ссылки